package org.btik.light.mobile.discovery;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.btik.light.tool.ByteUtil;

import java.io.IOException;
import java.net.*;
import java.nio.charset.StandardCharsets;

/**
 * 往组播地址发送topic名称 对端只需要确认topic来获取本机ip
 *
 * @author lustre
 * @since 2023/4/7 16:17
 */
public class ServerInfoPusher extends Thread {

    private final Log log = LogFactory.getLog(ServerInfoPusher.class);

    private boolean runFlag = true;

    private final byte[] topic = "light-server".getBytes(StandardCharsets.UTF_8);

    private int multicastPort;

    private final byte[] clientTopic = "light-client".getBytes(StandardCharsets.UTF_8);

    /**
     * 本机为热点时，组播大概率无法走这个热点，这个的端口用于接收下位机的消息
     */
    private int asGatewayPort;


    private final byte[] lock = new byte[0];

    private volatile boolean suspend = false;

    @Override
    public void run() {
        log.info("ServerInfoPusher start");
        DatagramSocket multicastSocket = null;
        DatagramPacket lightServerTopic = new DatagramPacket(topic, topic.length);
        lightServerTopic.setPort(multicastPort);
        InetAddress multicastGroupAddr;
        try {
            multicastSocket = new DatagramSocket(asGatewayPort);
            multicastSocket.setSoTimeout(100);
            multicastGroupAddr = InetAddress.getByName("224.0.0.5");
            lightServerTopic.setAddress(multicastGroupAddr);
        } catch (SocketException | UnknownHostException e) {
            log.info(e.getMessage(), e);
            runFlag = false;
            return;
        }
        DatagramPacket clientInfo = new DatagramPacket(new byte[32], 32);
        while (runFlag) {
            try {
                multicastSocket.send(lightServerTopic);
                synchronized (lock) {
                    if (suspend) {
                        //挂起，唤醒前才会继续发送
                        lock.wait();
                    } else {
                        lock.wait(600);
                    }

                }
                // 尝试读取客户的往热点发的包如果本机是热点的话
                if (timeReceive(multicastSocket, clientInfo)) {
                    int length = clientInfo.getLength();
                    if (length < clientTopic.length) {
                        clientInfo.setLength(0);
                        continue;
                    }
                    // 比较收到消息是否客户端topic
                    if (!ByteUtil.equals(clientInfo.getData(), clientInfo.getOffset(), clientTopic, 0, clientTopic.length)) {
                        clientInfo.setLength(0);
                        continue;
                    }
                    // 相对之后把 light-server 发给对端
                    try {
                        lightServerTopic.setAddress(clientInfo.getAddress());
                        lightServerTopic.setPort(clientInfo.getPort());
                        multicastSocket.send(lightServerTopic);
                    } finally {
                        // 发送后 把包地址改回组播
                        lightServerTopic.setPort(multicastPort);
                        lightServerTopic.setAddress(multicastGroupAddr);
                    }
                }
            } catch (IOException | InterruptedException e) {
                log.info(e.getMessage());
                if (log.isDebugEnabled()) {
                    log.debug("", e);
                }
            }

        }
    }

    private static boolean timeReceive(DatagramSocket udpSock, DatagramPacket clientMsg) throws IOException {
        try {
            udpSock.receive(clientMsg);
            return true;
        } catch (IOException e) {
            if (e instanceof SocketTimeoutException) {
                return false;
            }
            throw e;
        }
    }

    public void doSendSuspend() {
        synchronized (lock) {
            suspend = true;
        }
    }

    public void wakeUp() {
        synchronized (lock) {
            suspend = false;
            lock.notify();
        }
    }


    public void setMulticastPort(int multicastPort) {
        this.multicastPort = multicastPort;
    }

    public void setAsGatewayPort(int asGatewayPort) {
        this.asGatewayPort = asGatewayPort;
    }
}
